/*
 * Robust Pointers - A framework for robust pointers in a MT environment.
 * http://code.google.com/p/rptr/
 *
 * Copyright (c) 2009, Bill K. Barekas <bbarekas@gmail.com>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *    1. Redistributions of source code must retain the above copyright
 *       notice, this list of conditions and the following disclaimer.
 *    2. Redistributions in binary form must reproduce the above copyright
 *       notice, this list of conditions and the following disclaimer in the
 *       documentation and/or other materials provided with the distribution.
 *    3. Neither the name of the author nor the names of its
 *       contributors may be used to endorse or promote products derived
 *       from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/**
 * @file    rptr.c
 * @author  Bill K. Barekas <bbarekas@gmail.com>
 * @date    October 3, 2009
 * @version $Id$
 *
 * This file contains the definitions for the main functions of the
 * Robust PoinTeR library.
 */


#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <time.h>
#include <errno.h>

#include "rptr_impl.h"
#include "rptr_macros.h"
#include "rptr_error.h"


/**
 * Global variables.
 */
rptr_global                  rptr_data;      /* Global data structure. */
const uint32_t               min_type_ids = 0x80;/* Minimum type id. */


/*
 * Internal interface.
 */


/**
 * rptr_obj_create
 *
 * This function creates a new RPTR object and inserts it into the free queue.
 *
 * @params  none
 * @return  a pointer to the new RPTR object, or NULL if an error occurred
 */
rptr_i *
rptr_obj_create()
{
  rptr_i *         obj;
  int              ret;
  rptr_errno = RPTR_OK;

  /* Allocate memory for the object. */
  obj = malloc(sizeof(*obj));
  if (obj == NULL) {
    rptr_errno = RPTR_ERROR_COMMON;
    return NULL;
  }
  /* Initialize the object structure. */
  SET_VALUE(obj, 0);

  /* Initialize the object fields. */
  obj->u.type = 0;
  obj->u.id = rptr_data.num_objs++;
  obj->u.status = RPTR_FREE;
  obj->tstamp = 0;
  obj->ptr = NULL;

  /* Initialize the object mutex. */
  if (0 != (ret = pthread_mutex_init(&obj->lock, NULL))) {
    rptr_errno = RPTR_ERROR_COMMON;
    return NULL;
  }

  /**
    * NOTE:
    * Global RPTR mutex should be locked here.
    */

  /* Insert the object into the free queue. */
  TAILQ_INSERT_HEAD(&(rptr_data.free_queue), obj, queue_entry);
  rptr_data.free_objs++;

  return obj;
}


/**
 * rptr_objs_create
 *
 * This function creates many new RPTR objects by calling the rptr_obj_create
 * function, and updates internal counters.
 *
 * @params  num_objs    number of objects to create
 * @return  a pointer to the new RPTR object, or NULL if an error occurred
 */
int
rptr_objs_create(uint32_t num_objs)
{
  int              i;
  rptr_i *         obj;
  rptr_errno = RPTR_OK;

  for (i = 0; i < num_objs; i++) {
    if (NULL == (obj = rptr_obj_create())) {
      return i;
    }
  }
  assert(i == num_objs);

  /**
    * NOTE:
    * Global RPTR mutex should be locked here.
    */

  /* Set watermarks. */
  rptr_data.high_watermark = rptr_data.num_objs;
  if (rptr_data.free_objs > 2*rptr_data.inc_step_objs) {
    rptr_data.low_watermark = rptr_data.free_objs - rptr_data.inc_step_objs;
  }
  else {
    rptr_data.low_watermark = 0;
  }

  return i;
}


/*
 * External interface.
 */


/**
 * rptr_init
 *
 * This function initializes the RPTR framework.
 *
 * @params  conf    pointer to a RPTR configuration structure
 * @return  0 if successful, or -1 if an error occurred
 */
int
rptr_init(rptr_conf * conf)
{
  int              ret;
  rptr_errno = RPTR_OK;

  if (rptr_data.initialized) {
    /* Already initialized. */
    rptr_errno = RPTR_ERROR_INITIALIZED;
    return -1;
  }

  /* Initialize fields. */
  memset(&rptr_data, 0, sizeof(rptr_global));
  /* Initialize the mutex. */
  if (0 != (ret = pthread_mutex_init(&rptr_data.lock, NULL))) {
    rptr_errno = RPTR_ERROR_COMMON;
    return -1;
  }

  RPTR_GLOBAL_LOCK();

  /* Initialize free queue. */
  TAILQ_INIT(&(rptr_data.free_queue));
  rptr_data.free_objs = 0;
  /* Initialize types array.
   * Reserve the type with index 0*/
  rptr_data.types = malloc(sizeof(rptr_type));
  rptr_data.num_types = 0;
  rptr_data.type_ids = min_type_ids;
  /* Initialize stats. */
  rptr_data.num_objs = 0;
  rptr_data.num_objs_active = 0;
  rptr_data.num_objs_freed = 0;
  rptr_data.bytes_allocated = 0;
  rptr_data.bytes_net_allocated = 0;
  rptr_data.init_objs = conf->min_objs;
  rptr_data.inc_step_objs = conf->inc_step;
  rptr_data.low_watermark = 0;
  rptr_data.high_watermark = 0;
  rptr_data.reuse_period = conf->reuse_period;

  /* Create the initial objects. */
  ret = rptr_objs_create(rptr_data.init_objs);

  /* Set as initialized. */
  rptr_data.initialized = 1;

  RPTR_GLOBAL_UNLOCK();

  //printf("RPTR: Initialized. Size of rptr:%d\n", sizeof(rptr_i));

  return 0;
}


/**
 * rptr_new_type
 *
 * This function registers a new RPTR object type and allocates a RPTR typeid
 * for this particular type.
 *
 * @params  full_name   full name for the registering type
 * @params  abbr_name   abbreviated name for the registering type
 * @params  size        size in bytes registering type
 * @return  a typeid allocated for the new type, or -1 if an error occurred
 */
rptr_type
rptr_new_type(char * full_name, char * abbr_name, uint32_t size)
{
  rptr_type        typeid;
  uint32_t         index;
  rptr_errno = RPTR_OK;

  if (!rptr_data.initialized) {
    /* Not initialized. */
    rptr_errno = RPTR_ERROR_NOT_INITIALIZED;
    return -1;
  }

  RPTR_GLOBAL_LOCK();

  /* Allocate a new type in the array. */
  rptr_data.num_types++;
  typeid = ++rptr_data.type_ids;
  index = typeid - min_type_ids;
  assert(index == rptr_data.num_types);

  /**
   * NOTE:
   * Indices are always increasing so this index is the bigger one.
   */

  /* Resize types array. */
  rptr_data.types = realloc(rptr_data.types, (index + 1)*sizeof(rptr_type_i));
  assert(rptr_data.types != NULL);

  /* Initialize type. */
  rptr_data.types[index].id = typeid;
  rptr_data.types[index].index = index;
  rptr_data.types[index].full_name = strdup(full_name);
  rptr_data.types[index].abbr_name = strdup(abbr_name);
  rptr_data.types[index].size = size;
  rptr_data.types[index].num_objs = 0;
  rptr_data.types[index].num_objs_active = 0;
  rptr_data.types[index].num_objs_freed = 0;

  RPTR_GLOBAL_UNLOCK();

/*
  printf("RPTR: Registered Type:0x%02x I:%d N:""%s"" A:""%s"".\n",
      typeid, index,
      rptr_data.types[index].full_name,
      rptr_data.types[index].abbr_name);
*/

  return typeid;
}


/**
 * rptr_add
 *
 *
 */
rptr
rptr_add(rptr_type typeid, void * ptr, uint32_t id)
{
  rptr_i *         obj;
  va_list          args;
  int              ret;
  rptr_errno = RPTR_OK;

  if (!rptr_data.initialized) {
    /* Not initialized. */
    rptr_errno = RPTR_ERROR_NOT_INITIALIZED;
    return NULL;
  }

  if ((typeid < min_type_ids) || (typeid > rptr_data.type_ids)) {
    /* Invalid typeid. */
    rptr_errno = RPTR_ERROR_INVALID_RPTR_TYPE;
    return NULL;
  }

  RPTR_GLOBAL_LOCK();

  if (rptr_data.free_objs == 0) {
    /* Create more objects. */
    ret = rptr_objs_create(rptr_data.inc_step_objs);
  }

  /* Get a free object. */
  obj = TAILQ_FIRST(&(rptr_data.free_queue));
  assert(obj != NULL);
  TAILQ_REMOVE(&(rptr_data.free_queue), obj, queue_entry);
  rptr_data.free_objs--;
  rptr_data.types[typeid].num_objs++;
  rptr_data.types[typeid].num_objs_active++;

  RPTR_GLOBAL_UNLOCK();

  RPTR_OBJ_LOCK(obj);

  /* Assign it to the adding object. */
  obj->u.type = typeid;
  obj->u.id = id;
  obj->ptr = ptr;
  obj->u.status = RPTR_ALLOCATED;

  RPTR_OBJ_UNLOCK(obj);

  rptr_errno = 0;
  return (rptr)obj;
}


/**
 * rptr_delete
 *
 * TBD!!!!
 */
void
rptr_delete(rptr ptr, int error)
{
  rptr_i *          obj;
  rptr_type         typeid;
  int               ret;

  if (!rptr_data.initialized) {
    /* Not initialized. */
    printf("Not Initialized.\n");
    return;
  }
  assert(ptr != NULL);
  obj = (rptr_i *)ptr;

  RPTR_OBJ_LOCK(obj);

  typeid = obj->u.type;
  if (obj->status == RPTR_ALLOCATED) {

    obj->u.type = 0;
    obj->u.status = RPTR_FREE;
    obj->tstamp = time(NULL); // TODO: SHOULD BE CAPCLOCK !!!!!

    /* Clean it up. */
    //ret = rptr_data.types[typeid].destroy_func(obj->ptr, error);
    /* Free the allocated memory. */
    //free(obj->ptr);
    //obj->ptr = NULL;

    RPTR_GLOBAL_LOCK();

    /* Update stats. */
    rptr_data.types[typeid].num_objs--;
    rptr_data.types[typeid].num_objs_active--;

    /* Insert it into the free list. */
    TAILQ_INSERT_TAIL(&(rptr_data.free_queue), obj, queue_entry);
    rptr_data.free_objs++;

    RPTR_GLOBAL_UNLOCK();

  }
  else {
    printf("RPTR:%p Not Allocated.\n", obj);
  }
  RPTR_OBJ_UNLOCK(obj);

  return;
}


/**
 * rptr_get_id
 *
 * Returns the object ID for the specified object.
 *
 */
uint64_t
rptr_get_id(rptr ptr)
{
  uint64_t          res;
  rptr_i *          obj = (rptr_i *)ptr;

  RPTR_OBJ_LOCK(obj);
  if (obj->u.status == RPTR_ALLOCATED) {
    /* Still allocated. */

    /**
     * TODO:
     * Check timestamps.
     * Introduce ptr_id and check them.
     */

    res = obj->oid;
  }
  else {
    RPTR_OBJ_UNLOCK(obj);
    rptr_errno = RPTR_ERROR_OBJ_NOT_ALLOCATED;
    return 0;
  }
  RPTR_OBJ_UNLOCK(obj);

  rptr_errno = 0;
  return res;
}


/**
 * rptr_get_ptr
 *
 * Returns the user pointer for the specified object.
 *
 */
void *
rptr_get_ptr(rptr ptr, rptr_type type)
{
  void *            res = NULL;
  rptr_i *          obj = (rptr_i *)ptr;

  RPTR_OBJ_LOCK(obj);
  if (obj->status == RPTR_ALLOCATED) {
    /* Still allocated. */
    if (obj->u.type == type) {
      /* Correct type. */

      /**
       * TODO:
       * Check timestamps.
       * Introduce ptr_id and check them.
       */
      res = obj->ptr;
    }
    else {
      printf("RPTR:%p Wrong type.\n", obj);
    }
  }
  else {
    printf("RPTR:%p Not Allocated.\n", obj);
  }
  RPTR_OBJ_UNLOCK(obj);

  return res;
}


/**
 * rptr_stats
 *
 * Returns some statistics for the RPTR framework.
 *
 */
void
rptr_stats(char * str)
{
  char             stats[1024];
  int              stats_len;

  if (!rptr_data.initialized) {
    /* Not initialized. */
    printf("Not Initialized.\n");
    return;
  }

  RPTR_GLOBAL_LOCK();

  /* Print Stats. */
  stats_len = 0;
  stats_len += sprintf(&stats[stats_len], "------------------------------\n");
  stats_len += sprintf(&stats[stats_len], "Stats - [RPTR size:%d]\n--%s--\n", sizeof(rptr_i), str);
  stats_len += sprintf(&stats[stats_len], "Types: %d\n", rptr_data.num_types);
  stats_len += sprintf(&stats[stats_len], "Objects:  Total:%d\tFree:%d  \tActive:%d\tFreed:%d\n",
      rptr_data.num_objs, rptr_data.free_objs,
      rptr_data.num_objs_active, rptr_data.num_objs_freed);
  stats_len += sprintf(&stats[stats_len], "Marks:    High:%d\tLow:%d\n",
      rptr_data.high_watermark, rptr_data.low_watermark);
  stats_len += sprintf(&stats[stats_len], "Bytes:    Total:%d\tNet:%d\n",
      rptr_data.bytes_allocated, rptr_data.bytes_net_allocated);
  stats_len += sprintf(&stats[stats_len], "------------------------------\n");
  printf("%s", stats);

  RPTR_GLOBAL_UNLOCK();


  return;
}
